module("nginx_conf_parser", package.seeall)

local gcf = require("gray_conf")
local ngx_conf_prefix = gcf.ngx_conf_prefix

local LF = 10           -- '\n' huanhang
local CR = 13           -- huiche
local TAB = 9           -- '\t'
local SEMI = 59         -- semicolon ;
local SPACE = 32        -- ' '
local SHARP = 35        -- #
local DOLLAR = 36       -- '$'
local QUOTE = 92        -- \\
local SQUOTE = 39       -- "'"
local DQUOTE = 34       -- '"'
local LBRACE = 123      -- {
local RBRACE = 125      -- }
local RBRACKET = 41     -- )

-- TODO skip ' '  " "
-- buffer size
local CONF_BUFFER = 2^13

local char_map = {}
char_map[LF] = '\\n'
char_map[CR] = '\\r'
char_map[TAB] = '\\t'
char_map[SEMI] = ';'
char_map[SPACE] = 'space'
char_map[SHARP] = '#'
char_map[DOLLAR] = '$'
char_map[QUOTE] = '\\'
char_map[SQUOTE] = 'single quotes'
char_map[DQUOTE] = 'double quotes'
char_map[LBRACE] = '{'
char_map[RBRACE] = '}'
char_map[RBRACKET] = ')'

local CONF_NOARGS  =    0x00000001
local CONF_TAKE1   =    0x00000002
local CONF_TAKE2   =    0x00000004
local CONF_TAKE3   =    0x00000008
local CONF_TAKE4   =    0x00000010
local CONF_TAKE5   =    0x00000020
local CONF_TAKE6   =    0x00000040
local CONF_TAKE7   =    0x00000080

local CONF_MAX_ARGS  =  8

local CONF_TAKE12   =   12
local CONF_TAKE13   =   13
local CONF_TAKE23   =   23
local CONF_TAKE123  =   123
local CONF_TAKE1234 =   1234
local CONF_FLAG     =   0x00000200
local CONF_ANY      =   0x00000400
local CONF_1MORE    =   0x00000800
local CONF_2MORE    =   0x00001000


local argument_number = {
    {CONF_NOARGS},
    {CONF_TAKE1, CONF_TAKE12, CONF_TAKE123, CONF_TAKE1234},
    {CONF_TAKE2, CONF_TAKE12, CONF_TAKE123, CONF_TAKE1234, CONF_TAKE23},
    {CONF_TAKE3, CONF_TAKE123, CONF_TAKE1234, CONF_TAKE23},
    {CONF_TAKE4, CONF_TAKE1234},
    {CONF_TAKE5},
    {CONF_TAKE6},
    {CONF_TAKE7}
}


local CONF_BLOCK_START = 1
local CONF_BLOCK_DONE = 2
local CONF_FILE_DONE = 3

CORE_MODULE = 1
HTTP_MODULE = 2
-- CONF_MODULE = 3

MAIN_CONF = 1

HTTP_MAIN_CONF = 1
HTTP_SRV_CONF = 2
HTTP_LOC_CONF = 3
HTTP_UPS_CONF = 4
HTTP_SIF_CONF = 5
HTTP_LIF_CONF = 6
HTTP_GEO_CONF = 7

ANY_CONF = "1111"

local core_main_conf = {}
local http_main_conf = {}
local http_srv_conf = {}
local http_loc_conf = {}
local http_ups_conf = {}
local http_srv_loc_conf = {}
local http_loc_lif_conf = {}
local http_srv_sif_loc_lif_conf = {}
local http_main_srv_loc_lif_conf = {}

local include_type = {}

core_main_conf[MAIN_CONF] = 1
http_main_conf[HTTP_MAIN_CONF] = 1
http_srv_conf[HTTP_SRV_CONF] = 1
http_loc_conf[HTTP_LOC_CONF] = 1
http_ups_conf[HTTP_UPS_CONF] = 1
http_srv_loc_conf[HTTP_SRV_CONF] = 1
http_srv_loc_conf[HTTP_LOC_CONF] = 1
http_loc_lif_conf[HTTP_LIF_CONF] = 1
http_loc_lif_conf[HTTP_LOC_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_SRV_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_LOC_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_LIF_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_SIF_CONF] = 1
http_main_srv_loc_lif_conf[HTTP_MAIN_CONF] = 1
http_main_srv_loc_lif_conf[HTTP_SRV_CONF] = 1
http_main_srv_loc_lif_conf[HTTP_LOC_CONF] = 1
http_main_srv_loc_lif_conf[HTTP_LIF_CONF] = 1

include_type[MAIN_CONF] = 1
include_type[HTTP_MAIN_CONF] = 1
include_type[HTTP_SRV_CONF] = 1

-- not support
include_type[HTTP_LOC_CONF] = 1
include_type[HTTP_LIF_CONF] = 1
include_type[HTTP_SIF_CONF] = 1

local invalid_include_type = {}
invalid_include_type[HTTP_LOC_CONF] = "location"
invalid_include_type[HTTP_LIF_CONF] = "location if"
invalid_include_type[HTTP_SIF_CONF] = "server if"

local CONF_UNKNOWN = "1000"
local CONF_INCLUDE = "1001"

local parse_file = 0
local parse_block = 1

-- private functions
local conf_read_token
local conf_default_handler


function create_ngx_cf(module_type, cmd_type)
    local cf = {}
    cf.module_type = module_type
    cf.cmd_type = cmd_type
    cf.ngx_conf = {}

    -- to save all upstream/server/geo
    cf.ngx_conf_upstreams = {}
    cf.ngx_conf_servers = {}
    cf.ngx_conf_geos = {}

    cf.proxy_passes = {}

    cf.ngx_conf_server_name_map = {}
    cf.ngx_conf_ups_name_map = {}
    cf.access_by_lua = {}
    cf.includes = {}
    return cf
end


function ngx_conf_parse(cf, cf_file_name)
    local parse_type
    local rc, rv, f, m
    if cf_file_name then
        f, m = io.open(cf_file_name, "r")
        if f == nil then return nil, m end

        if cf.conf_file == nil then cf.conf_file = {} end
        if cf.conf_file.buffer == nil then cf.conf_file.buffer = {} end
        cf.conf_file.line = 1

        cf.conf_file.buffer.body = f:read("*all")
        cf.conf_file.buffer.pos = 1
        cf.conf_file.buffer.last = #cf.conf_file.buffer.body
        cf.conf_file.filename = cf_file_name

        f:close()
        parse_type = parse_file
    else
        parse_type = parse_block
    end

    while true do
        rc, m = conf_read_token(cf)

        if rc == nil then
            return nil, m
        end

        if rc == CONF_FILE_DONE then
            if parse_type == parse_block then
                return nil,  "unexpected end of file, expecting }"
            end
            return true
        end

        if rc == CONF_BLOCK_DONE then
            if parse_type ~= parse_block then
                return nil, "unexpected }"
            end
            return true
        end

        rc, m = conf_default_handler(cf, rc)

        if rc == nil then return nil, m end
    end

    return nil, "unknown reason"
end


function ngx_conf_to_file(ngx_conf, file_name, success_created_files)
    local function ngx_conf_write_to_file(ngx_conf, blanks, f)
        if ngx_conf == nil then return true end
        local r, m = ngx_conf_write_to_file(ngx_conf.HEAD, blanks, f)
        if r == nil then return nil, m end
        for j, directive in ipairs(ngx_conf) do
            local r, m = ngx_conf_write_to_file(directive.PREV, blanks, f)
            if r == nil then return nil, m end
            if not directive.DELETE then
                local line = blanks
                for i, arg in ipairs(directive.ARGS) do
                    if i ~= #directive.ARGS then
                        line = line..arg.." "
                    else
                        line = line..arg
                    end
                end
                if directive.BLOCK then
                    f:write(string.format("%s {\n", line))
                    local r, m = ngx_conf_write_to_file(directive.BLOCK, "    "..blanks, f)
                    if r == nil then return nil, m end
                    f:write(string.format("%s}\n", blanks))
                elseif directive.NGX_CONF and directive.RECREATE then
                    f:write(string.format("%s;\n", line))
                    local r, m = ngx_conf_to_file(directive.NGX_CONF, directive.ARGS[2], success_created_files)
                    if r == nil then return nil, m end
                else
                    if string.byte(directive.ARGS[1], 1) == SHARP then
                        f:write(string.format("%s\n", line))
                    else
                        f:write(string.format("%s;\n", line))
                    end
                end
            end
            local r, m = ngx_conf_write_to_file(directive.NEXT, blanks, f)
            if r == nil then return nil, m end
        end
        local r, m = ngx_conf_write_to_file(ngx_conf.TAIL, blanks, f)
        if r == nil then return nil, m end
        return true
    end

    local f, m = io.open(file_name, "w")
    if f == nil then return nil, m end
    if success_created_files then success_created_files[file_name] = 1 end
    local r, m = ngx_conf_write_to_file(ngx_conf, "", f)
    if r == nil then f:close(); return nil, m end
    f:close()
    return true
end


function get_server_name_map(cf)
    for _, server in ipairs(cf.ngx_conf_servers) do
        if server.server_name then
            local port
            if server.listen then
                port = server.listen.port
            else
                port = "80"
            end
            local unique_name_map = {}
            -- save the parent block of this server_name
            for sn in pairs(server.server_name.PARSE_CONF.names) do
                local sn_with_port = sn..":"..port
                cf.ngx_conf_server_name_map[sn_with_port] = server
                unique_name_map[sn] = sn_with_port
            end
            -- get unique name
            server.UNIQUE_NAME_MAP = unique_name_map
        end
    end
end


-- only for server directive
function copy_directive(d)
    if d == nil then return nil end
    local nd = {}
    nd.CTYPE = d.CTYPE
    nd.ARGS = {}
    for _, arg in ipairs(d.ARGS) do
        table.insert(nd.ARGS, arg)
    end
    if d.BLOCK then
        nd.BLOCK = {}
        for _, directive in ipairs(d.BLOCK) do
            table.insert(nd.BLOCK, copy_directive(directive))
        end
        nd.BLOCK.INDEX = d.BLOCK.INDEX
    end
    if d.NGX_CONF then
        nd.NGX_CONF = {}
        for _, directive in ipairs(d.NGX_CONF) do
            table.insert(nd.NGX_CONF, copy_directive(directive))
        end
        nd.NGX_CONF.INDEX = d.NGX_CONF.INDEX
    end
    local function copy_xxs(src, dst, xxs, yy)
        if src[xxs] then 
            if #src[xxs] > 0 then
                dst[xxs] = {}
                for _, xx in ipairs(src[xxs]) do
                    if yy then
                        table.insert(dst[xxs], dst[xx.INDEX][yy])
                        dst[xx.INDEX][yy].PARSE_CONF = xx.PARSE_CONF
                        dst[xx.INDEX][yy].INDEX = xx.INDEX
                        dst[xx.INDEX][yy].PARENT = dst
                    else
                        table.insert(dst[xxs], dst[xx.INDEX])
                        dst[xx.INDEX].PARSE_CONF = xx.PARSE_CONF
                        dst[xx.INDEX].INDEX = xx.INDEX
                        dst[xx.INDEX].PARENT = dst
                    end
                end
            else
                dst[xxs] = dst[src[xxs].INDEX]
                dst[xxs].PARSE_CONF = src[xxs].PARSE_CONF
                dst[xxs].INDEX = src[xxs].INDEX
                dst[xxs].PARENT = dst
            end
        end
    end
    if d.CTYPE == HTTP_SRV_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "ifs",       "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "includes",  "NGX_CONF")
        copy_xxs(d.BLOCK, nd.BLOCK, "locations", "BLOCK")
    elseif d.CTYPE == HTTP_LOC_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "locations", "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "ifs",       "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "proxy_pass")
        copy_xxs(d.BLOCK, nd.BLOCK, "sets")
    elseif d.CTYPE == HTTP_LIF_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "sets")
        copy_xxs(d.BLOCK, nd.BLOCK, "proxy_pass")
    elseif d.CTYPE == CONF_INCLUDE then
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "ifs",       "BLOCK")
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "includes",  "NGX_CONF")
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "locations", "BLOCK")
    end

    return nd
end


function create_directive(name, args, block)
    local directive = {}; directive.ARGS = {name}
    if type(args) == 'string' then table.insert(directive.ARGS, args) end
    if type(args) == 'table' then
        for _, v in ipairs(args) do
            table.insert(directive.ARGS, v)
        end
    end
    if block then directive.BLOCK = {} end
    return directive
end


function ngx_conf_insert(ngx_conf, directive)
    table.insert(ngx_conf, directive)
end


function ngx_conf_back_to_directive(ngx_conf)
    return ngx_conf.PARENT[ngx_conf.INDEX]
end


function ngx_conf_directive_delete(directive)
    directive.DELETE = true
end


function ngx_conf_directive_insert_head(directive, new_directive)
    if directive.PREV == nil then directive.PREV = {} end
    table.insert(directive.PREV, new_directive)
end


function ngx_conf_directive_insert_tail(directive, new_directive)
    if directive.NEXT == nil then directive.NEXT = {} end
    table.insert(directive.NEXT, new_directive)
end


function ngx_conf_insert_head(ngx_conf, directive)
    if ngx_conf.HEAD == nil then
        ngx_conf.HEAD = {}
    end
    table.insert(ngx_conf.HEAD, directive)
end
 

function ngx_conf_insert_tail(ngx_conf, directive)
    if ngx_conf.TAIL == nil then
        ngx_conf.TAIL = {}
    end
    table.insert(ngx_conf.TAIL, directive)
end


local function http_block(cf, cmd)
    local module_type = cf.module_type
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf

    local directive = {}
    local http = {}

    if ngx_conf.INCLUDE then
        return nil, "not support http block included in a file"
    end

    cf.ngx_conf_http = http

    directive.ARGS = cf.args
    directive.BLOCK = http

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save parent block (core or include)
    http.PARENT = cf.ngx_conf
    http.INDEX = #cf.ngx_conf
    directive.CTYPE = HTTP_MAIN_CONF

    cf.module_type = HTTP_MODULE
    cf.cmd_type = HTTP_MAIN_CONF

    -- ngx_conf.http = http
    cf.ngx_conf = http

    -- change current context (http or server)
    local context = cf.context
    cf.context = http
    cf.http = http

    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.context = context
    cf.module_type = module_type
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function include_file_not_ignored(filename)
    if not filename then return true end
    local ignored_files = {}
    ignored_files["mime.types"] = true
    ignored_files["tmd_main.conf"] = true
    ignored_files["tmd_http.conf"] = true
    ignored_files["tmd_loc.conf"] = true
    ignored_files["tmd_ip.conf"] = true
    ignored_files["tmd3_main.conf"] = true
    ignored_files["tmd3_http.conf"] = true
    ignored_files["tmd3_loc.conf"] = true
    ignored_files["tmd3_ip.conf"] = true
    ignored_files["tmd3_loc_wireless.conf"] = true
    ignored_files["tmd4_domain.conf"] = true
    ignored_files["tmd4_http.conf"] = true
    ignored_files["tmd4_loc.conf"] = true
    ignored_files["tmd4_main.conf"] = true
    local basename = string.match(filename, "([^/]+)$") 
    if not basename then return true end
    return not ignored_files[basename]
end


local function http_rewrite_if(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local if_block = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = if_block

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    if_block.INDEX = #cf.ngx_conf
    if_block.PARENT = cf.ngx_conf
    if_block.ARGS = cf.args

    if ngx_conf.INCLUDE and not ngx_conf.PARENT.SERVER then
        return nil, "not support if block in a location included in a file alone"
    end

    if ngx_conf.ifs == nil then
        ngx_conf.ifs = {}
    end

    ngx_conf.ifs[#ngx_conf.ifs + 1] = if_block

    if cmd_type == HTTP_SRV_CONF then
        directive.CTYPE = HTTP_SIF_CONF
        cf.cmd_type = HTTP_SIF_CONF
    else
        directive.CTYPE = HTTP_LIF_CONF
        cf.cmd_type = HTTP_LIF_CONF
    end
    cf.ngx_conf = if_block
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function http_geo_block(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local geo = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = geo

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    geo.INDEX = #cf.ngx_conf
    geo.PARENT = cf.ngx_conf
    geo.ARGS = cf.args
    directive.CTYPE= HTTP_GEO_CONF

    -- parse
    local parse_conf = {}
    parse_conf.var_name = cf.args[#cf.args]
    geo.PARSE_CONF = parse_conf

    if ngx_conf.geos == nil then
        ngx_conf.geos = {}
    end

    ngx_conf.geos[#ngx_conf.geos + 1] = geo

    cf.ngx_conf_geos[#cf.ngx_conf_geos + 1] = geo

    cf.cmd_type = HTTP_GEO_CONF
    cf.ngx_conf = geo
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function http_upstream_block(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local upstream = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = upstream

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    upstream.INDEX = #cf.ngx_conf
    upstream.PARENT = cf.ngx_conf
    upstream.ARGS = cf.args

    directive.CTYPE= HTTP_UPS_CONF

    -- parse
    local parse_conf = {}
    parse_conf.name = cf.args[#cf.args]
    upstream.PARSE_CONF = parse_conf

    cf.ngx_conf_ups_name_map[parse_conf.name] = upstream

    if cf.http.upstreams == nil then
        cf.http.upstreams = {}
    end

    cf.http.upstreams[parse_conf.name] = upstream

    cf.ngx_conf_upstreams[#cf.ngx_conf_upstreams + 1] = upstream

    cf.cmd_type = HTTP_UPS_CONF
    cf.ngx_conf = upstream
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function http_upstream_server(cf, cmd)
    local upstream = cf.ngx_conf
    if upstream.INCLUDE then
        return nil, "not support directive server included in a file alone"
    end
    local directive = {}
    directive.ARGS = cf.args
    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf
    if cf.args[3] ~= "backup" then
        if not upstream.servers then
            upstream.servers = {}
        end
        upstream.servers[cf.args[2]] = directive
    end
    --table.insert(upstream.servers, directive)
    return true
end


local function http_upstream_ip_hash(cf, cmd)
    local upstream = cf.ngx_conf
    if upstream.INCLUDE then
        return nil, "not support directive server included in a file alone"
    end
    local directive = {}
    directive.ARGS = cf.args
    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf
    upstream.ip_hash = directive
    return true
end


local function http_server_block(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local server = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = server

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save the position of this server in http block
    server.INDEX = #cf.ngx_conf

    -- save parent block (http or include)
    server.PARENT = cf.ngx_conf

    server.SERVER = 1

    directive.CTYPE= HTTP_SRV_CONF

    -- record exact locations
    local parse_conf = {}
    parse_conf.exact_loc_map = {}
    server.PARSE_CONF = parse_conf

    -- new servers table
    if ngx_conf.servers == nil then
        ngx_conf.servers = {}
    end

    ngx_conf.servers[#ngx_conf.servers + 1] = server

    -- save all servers in global ngx_conf_servers
    cf.ngx_conf_servers[#cf.ngx_conf_servers + 1] = server

    cf.cmd_type = HTTP_SRV_CONF
    cf.ngx_conf = server

    -- current context (http or server)
    local context = cf.context
    cf.context = server

    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.context = context
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function http_location_block(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local location = {}

    location.CF = cf
    location.ARGS = cf.args

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = location

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save the position of this location in server/include block
    location.INDEX = #cf.ngx_conf

    location.PARENT = cf.ngx_conf

    directive.CTYPE= HTTP_LOC_CONF

    -- new locations table
    if ngx_conf.locations == nil then
        ngx_conf.locations = {}
    end
    -- parse, for now only exact location is parsed
    local parse_conf = {}
    if #cf.args == 3 then
        parse_conf.name = cf.args[2]..cf.args[3]
    else
        parse_conf.name = cf.args[2]
    end
    location.PARSE_CONF = parse_conf

    table.insert(ngx_conf.locations, location)

    cf.cmd_type = HTTP_LOC_CONF
    cf.ngx_conf = location
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return true
end


local function conf_include(cf, cmd)
    -- TODO include  mime.types; 
    if cf.ngx_conf.INCLUDE then
        return nil, "not support nested include file " .. tostring(cf.conf_file.filename)
    end
    if invalid_include_type[cf.cmd_type] then
        return nil, "not support include file in "..invalid_include_type[cf.cmd_type]
    end
    local ngx_conf = cf.ngx_conf
    local conf_file =  cf.conf_file
    local args = cf.args

    local full_name = cf.args[2]
    local inc_ngx_conf = {}
    local directive = {}
    directive.ARGS = args
    ngx_conf[#ngx_conf + 1] = directive

    if string.match(full_name, "%*$") then
        return nil, "not support * in include line"
    end

    directive.CTYPE= CONF_INCLUDE
    directive.NGX_CONF = inc_ngx_conf

    inc_ngx_conf.INDEX = #ngx_conf
    inc_ngx_conf.PARENT = ngx_conf
    inc_ngx_conf.INCLUDE = 1

    local parse_conf = {}
    if cf.cmd_type == HTTP_SRV_CONF then
        parse_conf.exact_loc_map = {}
    end
    parse_conf.file_name = args[2]
    inc_ngx_conf.PARSE_CONF = parse_conf

    -- change to include ngx_conf
    cf.ngx_conf = inc_ngx_conf
    cf.conf_file = nil
    cf.args = nil

    -- save include ngx_conf into includes table
    if ngx_conf.includes == nil then
        ngx_conf.includes = {}
    end
    ngx_conf.includes[#ngx_conf.includes + 1] = inc_ngx_conf

    cf.includes[#cf.includes + 1] = inc_ngx_conf

    if string.sub(full_name, 1, 1) ~= '/' then
        full_name = ngx_conf_prefix .. full_name
    end

    local rc, m = ngx_conf_parse(cf, full_name)
    if rc == nil then
        return nil, m
    end

    cf.ngx_conf = ngx_conf
    cf.conf_file = conf_file
    cf.args = args

    return true
end


local function http_server_name(cf, cmd)
    local parse_conf
    local server_block = cf.ngx_conf
    if server_block.INCLUDE then
        return nil, "not support server_name included in a file alone"
    end
    local directive
    -- combine all server_name
    if server_block.server_name then
        parse_conf = server_block.server_name.PARSE_CONF
        directive = server_block.server_name
    else
        directive = {}
        directive.ARGS = {"server_name"}
        cf.ngx_conf[#cf.ngx_conf + 1] = directive
        -- save index in ngx_conf
        directive.INDEX = #cf.ngx_conf
        directive.PARENT = cf.ngx_conf
        parse_conf = {}
        parse_conf.names = {}
        directive.PARSE_CONF = parse_conf
        server_block.server_name = directive
    end
    for i, sn in ipairs(cf.args) do
        if i > 1 then
            parse_conf.names[sn] = 1
            table.insert(directive.ARGS, sn)
        end
    end
    return true
end


local function http_access_by_lua(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args
    cf.access_by_lua[#cf.access_by_lua + 1] = directive
    if not cf.context.access_by_lua then
        cf.context.access_by_lua = {}
    end
    cf.context.access_by_lua[#cf.context.access_by_lua + 1] = directive
    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf
    return true
end


local function http_server_listen(cf, cmd)
    local server_block = cf.ngx_conf
    if server_block.INCLUDE then
        return nil, "not support directive listen included in a file alone"
    end
    local directive = {}
    directive.ARGS = cf.args
    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf
    -- parse
    local parse_conf = {}
    local raw_port = cf.args[2]
    -- listen 8080; or listen 192.3.34.1:8080;
    if string.match(raw_port, "^%d+$") then
        parse_conf.port = raw_port
    elseif string.match(raw_port, "[^:]+%:%d+$") then
        parse_conf.port = string.match(raw_port, "[^:]+%:(%d+)$")
    else
        return nil, "not support this format of directive listen "..raw_port
    end
    for _, arg in ipairs(cf.args) do
       if arg == "default" or arg == "default_server" then
           parse_conf.default = 1
       end
    end
    directive.PARSE_CONF = parse_conf
    if not server_block.listen then
        server_block.listen = {}
        --server_block.listen.ports = {}
    end
    if server_block.listen.port and server_block.listen.port ~= parse_conf.port then
        return nil, "not support different port in the same server block"
    end
    server_block.listen.port = parse_conf.port
    table.insert(server_block.listen, directive)
    --server_block.listen.ports[parse_conf.port] = 1
    return true
end


local function http_proxy_pass(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    -- parse
    local parse_conf = {}
    local host = string.match(cf.args[2], "^http%://(%S+)")
    if host and not string.match(host, "^unix:") then
        parse_conf.host = host
    end

    directive.PARSE_CONF = parse_conf

    local loc_or_lif_block = cf.ngx_conf
    if loc_or_lif_block.INCLUDE then
        return nil, "not support proxy_pass included in a file alone"
    end

    loc_or_lif_block.proxy_pass = directive

    if not cf.context.proxy_passes then cf.context.proxy_passes = {} end
    table.insert(cf.context.proxy_passes, directive)

    return true
end


local function http_rewrite_set(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    local ngx_conf = cf.ngx_conf
    if ngx_conf.INCLUDE and not ngx_conf.PARENT.SERVER then
        return nil, "not support set (in location or if) can not included in a file alone"
    end

    -- new sets table
    if ngx_conf.sets == nil then
        ngx_conf.sets = {}
    end

    -- parse
    local parse_conf = {}
    parse_conf.var_name = cf.args[2]
    parse_conf.value = cf.args[3]

    directive.PARSE_CONF = parse_conf

    ngx_conf.sets[#ngx_conf.sets + 1] = directive

    return true
end


-- commands
local modules = {}
modules[CORE_MODULE] = {}
modules[HTTP_MODULE] = {}
--modules[CONF_MODULE] = {}

--local cmds = modules[CONF_MODULE]

local cmds = modules[CORE_MODULE]
cmds[#cmds + 1] = {name = "http", ctype = core_main_conf, argc = CONF_NOARGS, is_block = 1, set = http_block}
cmds[#cmds + 1] = {name = "include", ctype = ANY_CONF, argc = CONF_TAKE1, set = conf_include}

cmds = modules[HTTP_MODULE]

cmds[#cmds + 1] = {name = "include", ctype = ANY_CONF, argc = CONF_TAKE1, set = conf_include}
cmds[#cmds + 1] = {name = "geo", ctype = http_main_conf, argc = CONF_TAKE12, is_block = 1, set = http_geo_block}
cmds[#cmds + 1] = {name = "server", ctype = http_main_conf, argc = CONF_NOARGS, is_block = 1, set = http_server_block}
cmds[#cmds + 1] = {name = "listen", ctype = http_srv_conf, argc = CONF_1MORE, set = http_server_listen}
cmds[#cmds + 1] = {name = "server_name", ctype = http_srv_conf, argc = CONF_1MORE, set = http_server_name}
cmds[#cmds + 1] = {name = "access_by_lua", ctype = http_main_srv_loc_lif_conf, argc = CONF_TAKE1, set = http_access_by_lua}
cmds[#cmds + 1] = {name = "access_by_lua_file", ctype = http_main_srv_loc_lif_conf, argc = CONF_TAKE1, set = http_access_by_lua}
cmds[#cmds + 1] = {name = "location", ctype = http_srv_loc_conf, argc = CONF_TAKE12, is_block = 1, set = http_location_block}
cmds[#cmds + 1] = {name = "set", ctype = http_srv_sif_loc_lif_conf, argc = CONF_TAKE2, set = http_rewrite_set}
cmds[#cmds + 1] = {name = "if", ctype = http_srv_loc_conf, argc = CONF_1MORE, is_block = 1, set = http_rewrite_if}
cmds[#cmds + 1] = {name = "proxy_pass", ctype = http_loc_lif_conf, argc = CONF_TAKE1, set = http_proxy_pass}
cmds[#cmds + 1] = {name = "upstream", ctype = http_main_conf, argc = CONF_TAKE1, is_block = 1, set = http_upstream_block}
cmds[#cmds + 1] = {name = "server", ctype = http_ups_conf, argc = CONF_1MORE, set = http_upstream_server}
cmds[#cmds + 1] = {name = "ip_hash", ctype = http_ups_conf, argc = CONF_NOARGS, set = http_upstream_ip_hash}


conf_read_token = function(cf)
    local found = nil
    local need_space = nil
    local last_space = 1;
    local sharp_comment = nil
    local variable = nil
    local quoted = nil
    local s_quoted = nil
    local d_quoted = nil

    local len, ch
    local b = cf.conf_file.buffer;
    local start = b.pos;
    local start_line = cf.conf_file.line;
    cf.args = {}

    while true do
        repeat

        if b.pos > b.last then 
            if #cf.args > 0 then
                return nil, "unexpected end of file, expecting ; or }";
            end
            if sharp_comment and #cf.args == 0 then 
                cf.args[#cf.args + 1] = string.sub(b.body, start, b.pos - 1)
                return true
            end
            return CONF_FILE_DONE
        end

        len = b.pos - start;

        if len >= CONF_BUFFER then 
            cf.conf_file.line = start_line;

            if d_quoted then 
                ch = '"';

            elseif s_quoted then
                ch = '\'';

            else 
                return nil, "too long parameter "..string.sub(b.body, start, start + 10).." started"
            end 

            return nil, "too long parameter, probably missing terminating "..char_map[ch].." character"
        end

        ch = string.byte(b.body, b.pos)
        b.pos = b.pos + 1

        if ch == LF then
            cf.conf_file.line = cf.conf_file.line + 1

            if sharp_comment and #cf.args == 0 then 
                cf.args[#cf.args + 1] = string.sub(b.body, start, b.pos - 2)
                return true
            else
                sharp_comment = nil
            end
        end

        if sharp_comment then
            break
        end

        if quoted then
            quoted = nil
            break
        end

        if need_space then
            if ch == SPACE or ch == TAB or ch == CR or ch == LF then
                last_space = 1;
                need_space = nil;
                break
            end

            if ch == SEMI then
                return true
            end

            if ch == LBRACE then
                return CONF_BLOCK_START;
            end

            if ch == RBRACKET then
                last_space = 1
                need_space = nil
            else 
                 return nil, "unexpected char "..char_map[ch]
            end
        end

        if last_space then
            if ch == SPACE or ch == TAB or ch == CR or ch == LF then
                break
            end

            start = b.pos - 1;
            start_line = cf.conf_file.line;

            if ch == SEMI or ch == LBRACE then
                if #cf.args == 0 then
                    return nil, "unexpected char "..char_map[ch]
                end

                if ch == LBRACE then
                    return CONF_BLOCK_START;
                end

                return true
            elseif ch == RBRACE then
                if #cf.args ~= 0 then
                    return nil, "unexpected char }"
                end

                return CONF_BLOCK_DONE;
            elseif ch == SHARP then
                sharp_comment = 1;
                break

            elseif ch == QUOTE then
                quoted = 1;
                last_space = nil
                break

            elseif ch == DQUOTE then
                -- start = start + 1
                d_quoted = 1;
                last_space = nil
                break

            elseif ch == SQUOTE then
                -- start = start + 1
                s_quoted = 1;
                last_space = nil
                break
            else
                last_space = nil
            end

        else
            if ch == LBRACE and variable then
                break
            end

            variable = nil

            if ch == QUOTE then
                quoted = 1;
                break
            end

            if ch == DOLLAR then
                variable = 1;
                break
            end

            if d_quoted then
                if ch == DQUOTE then
                    d_quoted = nil
                    need_space = 1;
                    found = 0
                end

            elseif s_quoted then
                if ch == SQUOTE then
                    s_quoted = nil
                    need_space = 1;
                    found = 0
                end

            elseif ch == SPACE or ch == TAB or ch == CR or ch == LF
                     or ch == SEMI or ch == LBRACE then
                last_space = 1;
                found = 1;
            end

            if found then
                cf.args[#cf.args + 1] = string.sub(b.body, start, b.pos - found - 1)
                if ch == SEMI then
                    return true
                end

                if ch == LBRACE then
                    return CONF_BLOCK_START;
                end

                found = nil
            end
        end
        until true
    end
end


conf_default_handler = function(cf, rc)
    for _, cmd in ipairs(modules[cf.module_type]) do
        if cmd.name == cf.args[1] then
            if (cmd.ctype == ANY_CONF and include_type[cf.cmd_type] and include_file_not_ignored(cf.args[2]))
               or cmd.ctype[cf.cmd_type] then
                if cmd.is_block == nil and rc ~= true then
                    return nil, string.format("directive %s is not terminated by ;", cmd.name)
                end
                if cmd.is_block and rc ~= CONF_BLOCK_START then
                    return nil, string.format("directive %s has no opening {", cmd.name)
                end
                if cmd.argc ~= CONF_ANY then
                    if cmd.argc == CONF_FLAG then
                        if #cf.args ~= 2 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif cmd.argc == CONF_1MORE then

                        if #cf.args < 2 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif cmd.argc == CONF_2MORE then

                        if #cf.args < 3 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif #cf.args > CONF_MAX_ARGS then

                        return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)

                    else
                        local found
                        for _, argc in ipairs(argument_number[#cf.args]) do
                            if cmd.argc == argc then
                                found = 1
                                break
                            end
                        end
                        if not found then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end
                    end
                end

                local rv, m = cmd.set(cf, cmd)

                if rv == true then return true end
                if rv == nil then return nil, m end
                return nil, string.format("set command %s failed", cmd.name)
            end
        end
    end

    -- unknown directives, just record it
    local directive = {}
    directive.ARGS = cf.args
    directive.CTYPE = CONF_UNKNOWN

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    if rc == CONF_BLOCK_START then
        directive.BLOCK = {}
        local module_type = cf.module_type
        local cmd_type = cf.cmd_type
        local ngx_conf = cf.ngx_conf
        cf.cmd_type = CONF_UNKNOWN
        cf.ngx_conf = directive.BLOCK
        local r, m = ngx_conf_parse(cf)
        if not r then return nil, m end
        cf.cmd_type = cmd_type
        cf.ngx_conf = ngx_conf
    end

    return true
end

getmetatable(nginx_conf_parser).__newindex = function (table, key, val)
    error('Attempt to write to undeclared variable "' .. key .. '": '
          .. debug.traceback())
end

